package ufmg.maze.ga;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import ufmg.maze.Maze;

public class Individuo {

	public static final byte NORTE = 0;
	public static final byte SUL = 1;
	public static final byte LESTE = 2;
	public static final byte OESTE = 3;

	private static final Random rand = new Random();

	private boolean out;
	private byte[] genome;
	private int indiceFinal;
	private int iPos, jPos;
	Double valorFit = null;

	public int getIPos()
	{
		return iPos;
	}
	
	public int getJPos()
	{
		return jPos;
	}
	
	public Individuo(int tamanho) {
		genome = new byte[tamanho];
		indiceFinal = -1;

	}

	public Individuo(byte[] genome) {
		this.genome = genome;
	}

	public byte[] getGenome() {
		return genome;
	}

	private static byte getDirecao() {
		int direcao = rand.nextInt(4);
		switch (direcao) {
		case 0:
			return NORTE;
		case 1:
			return SUL;
		case 2:
			return LESTE;
		default:
			return OESTE;
		}
	}

	private byte deslocamentoX(byte movimento) {
		if (movimento == LESTE) {
			return 1;
		} else if (movimento == OESTE) {
			return -1;
		}
		return 0;
	}

	private byte deslocamentoY(byte movimento) {
		if (movimento == NORTE) {
			return -1;
		} else if (movimento == SUL) {
			return 1;
		}
		return 0;
	}

	private void popular(Maze maze) {
		byte posX = 0;
		byte posY = 0;
		indiceFinal = 0;

		int tabX = maze.getInicialColuna();
		int tabY = maze.getInicialLinha();

		for (int k = 0; k < genome.length; k++) {
			byte direcao = 0;
			byte newPosX = 0;
			byte newPosY = 0;
			boolean b1 = false;
			boolean b2 = false;

			do {
				direcao = getDirecao();

				newPosX = deslocamentoX(direcao);
				newPosY = deslocamentoY(direcao);
				b1 = false;
				b2 = false;
				if ((newPosX + posX == 0) && (newPosY + posY == 0)) {
					b1 = true;
				}

				tabX += 2 * newPosX;
				tabY += 2 * newPosY;

				if ((tabX < 0 || tabX > maze.getLimiteX())
						|| (tabY < 0 || tabY > maze.getLimiteY())) {
					b2 = true;
					tabX -= 2 * newPosX;
					tabY -= 2 * newPosY;
				}
			} while (b1 || b2);

			posX = newPosX;
			posY = newPosY;

			iPos = tabY;
			jPos = tabX;
			genome[k] = direcao;
			indiceFinal = k;
		}
	}

	public static Individuo criarIndividuo(int tamanho, Maze maze) {
		Individuo individuo = new Individuo(tamanho);
		individuo.popular(maze);
		return individuo;
	}

	public double fitness(Maze maze) {
		if (valorFit == null) {
			iPos = maze.getInicialLinha();
			jPos = maze.getInicialColuna();
			indiceFinal = 0;
			valorFit = 0.0;
			int desXAnt = 0;
			int desYAnt = 0;

			for (int k = 0; k < this.genome.length; k++) {
				int desX = deslocamentoX(genome[k]);
				int desY = deslocamentoY(genome[k]);

				if (!(desX + desXAnt == 0 && desY + desYAnt == 0)
						&& maze.isValidMove(iPos, jPos, genome[k])) {
					desXAnt = desX;
					desYAnt = desY;
					switch (genome[k]) {
					case Individuo.NORTE:
						iPos = iPos - 2;
						break;
					case Individuo.SUL:
						iPos = iPos + 2;
						break;
					case Individuo.LESTE:
						jPos = jPos + 2;
						break;
					case Individuo.OESTE:
						jPos = jPos - 2;
						break;
					}
					valorFit += 1.0;
					indiceFinal++;

					if (iPos == maze.getSaidaLinha()
							&& jPos == maze.getSaidaColuna()) {
						valorFit = (double) indiceFinal;
						return valorFit;
					}
				}
				else
				{
					valorFit+=10;
				}
			}
		}
		double aux = 0;
		if (iPos == maze.getSaidaLinha()
				&& jPos == maze.getSaidaColuna()) {
			aux = (double) indiceFinal;
		}
		else
		{
			if(valorFit==0.0)
				aux = genome.length*2;
			else
				aux = genome.length + valorFit;
		}
		return aux;
	}

	public int getIndiceFinal() {
		return indiceFinal;
	}

	public List<Individuo> cross(Individuo mother) {
		Random prob = new Random();
		ArrayList<Individuo> childs = new ArrayList<Individuo>();
		if (prob.nextInt(101) <= GeneticAlgorithm.CROSS_PROB) {
			int particao = rand.nextInt(this.genome.length);

			byte[] rightNewGenome = new byte[this.genome.length];
			byte[] leftNewGenome = new byte[this.genome.length];

			byte[] motherGenome = mother.getGenome();

			for (int i = 0; i < particao; i++) {
				rightNewGenome[i] = this.genome[i];
				leftNewGenome[i] = motherGenome[i];
			}

			for (int i = particao; i < this.genome.length; i++) {
				rightNewGenome[i] = motherGenome[i];
				leftNewGenome[i] = this.genome[i];
			}
			Individuo ind1 = new Individuo(leftNewGenome);
			Individuo ind2 = new Individuo(rightNewGenome);

			childs.add(ind1);
			childs.add(ind2);
		}
		return childs;
	}

	public void desloca(byte direcao) {
		switch (direcao) {
		case Individuo.NORTE:
			iPos = iPos - 2;
			break;
		case Individuo.SUL:
			iPos = iPos + 2;
			break;
		case Individuo.LESTE:
			jPos = jPos + 2;
			break;
		case Individuo.OESTE:
			jPos = jPos - 2;
			break;
		}
	}

	public void mutate() {
		Random prob = new Random();
		for (int i = 0; i < this.genome.length; i++) {
			int direcao = rand.nextInt(4);
			if (prob.nextInt(101) <= GeneticAlgorithm.MUTACAO_PROB) {
				switch (direcao) {
				case 0:
					genome[i] = NORTE;
					break;
				case 1:
					genome[i] = SUL;
					break;
				case 2:
					genome[i] = LESTE;
					break;
				default:
					genome[i] = OESTE;
				}
			}
		}
		valorFit = null;
	}

	public boolean isOut() {
		return out;
	}

	public void print(Maze maze) {
		int x = maze.getInicialColuna();
		int y = maze.getInicialLinha();
		if (indiceFinal == -1) {
			System.out.println("Sem solução!");

		} else {
			int desXAnt = 0;
			int desYAnt = 0;
			for (int i = 0; i < genome.length; i++) {
				int desX = deslocamentoX(genome[i]);
				int desY = deslocamentoY(genome[i]);

				if (!(desX + desXAnt == 0 && desY + desYAnt == 0)
						&& maze.isValidMove(y, x, genome[i])) {

					desXAnt = desX;
					desYAnt = desY;
					printDirecao(genome[i]);
					switch (genome[i]) {
					case Individuo.NORTE:
						y = y - 2;
						break;
					case Individuo.SUL:
						y = y + 2;
						break;
					case Individuo.LESTE:
						x = x + 2;
						break;
					case Individuo.OESTE:
						x = x - 2;
						break;
					}
				}
			}
			System.out.println();
		}
	}

	private void printDirecao(int direcao) {
		switch (direcao) {
		case NORTE:
			System.out.print("N ");
			break;
		case SUL:
			System.out.print("S ");
			break;
		case LESTE:
			System.out.print("L ");
			break;
		case OESTE:
			System.out.print("O ");
			break;
		}
	}
}
